package org.dreamwork.dsi.embedded.httpd.support;

import com.google.gson.Gson;
import org.dreamwork.gson.GsonHelper;
import org.dreamwork.injection.IObjectContext;
import org.dreamwork.util.IOUtil;
import org.dreamwork.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@WebServlet (urlPatterns = "/backend/*", loadOnStartup = 1)
public class BackendServlet extends HttpServlet {
    private final Logger logger = LoggerFactory.getLogger (BackendServlet.class);

    private WebHandlerScanner scanner;

    private IObjectContext context;

    @Override
    public void init () throws ServletException {
        super.init ();

        context = (IObjectContext) getServletContext ().getAttribute (IObjectContext.class.getCanonicalName ());
        scanner = context.getBean (WebHandlerScanner.class);
        logger.info ("backend servlet initialed.");
    }

    @Override
    protected void service (HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        String method   = request.getMethod ();
        String pathInfo = request.getPathInfo ();
        if (StringUtil.isEmpty (pathInfo)) {
            response.setStatus (HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        Map<String, String> values = new HashMap<> ();
        WebHandler handler = scanner.match (pathInfo, method.toLowerCase (), values);
        if (handler == null) {
            response.setStatus (HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        Object bean = context.getBean (handler.beanName);
        Object value;
        Gson g = new Gson ();
        HttpContext ctx = null;
        try {
            System.out.println (request.getSession ().getId ());

            ctx = new HttpContext (request, response);
            if (handler.method.parameters == null) {
                value = handler.method.invoke (bean);
            } else {
                Object[] args = parseParameters (request, handler, values);
                value = handler.method.invoke (bean, args);
            }
            response.setContentType (handler.method.contentType);
            if (handler.method.contentType.contains ("json")) {
                WebJsonResult wjr = new WebJsonResult (0, "success", value);
                response.getWriter ().write (g.toJson (wjr));
            } else {
                response.getWriter ().write (values.toString ());
            }
        } catch (InvocationTargetException ite) {
            Throwable t = ite.getCause ();
            if (handler.method.contentType.contains ("json")) {
                if (t instanceof WebHandlerException) {
                    WebHandlerException whe = (WebHandlerException) t;
                    WebJsonResult wjr = new WebJsonResult (whe.code, whe.getMessage (), null);
                    response.getWriter ().write (g.toJson (wjr));
                } else {
                    throw new ServletException (t);
                }
            } else {
                throw new ServletException (t);
            }
        } finally {
            if (ctx != null) {
                ctx.dispose ();
            }
        }
    }

    private Object[] parseParameters (HttpServletRequest request, WebHandler handler, Map<String, String> values) throws IOException {
        Object[] args = new Object[handler.method.parameters.size ()];
        for (int i = 0; i < handler.method.parameters.size (); i ++) {
            WebParameter wp = handler.method.parameters.get (i);
            String temp;
            switch (wp.location) {
                case QueryString:
                    temp = request.getParameter (wp.name);
                    break;
                case Body:
                    String contentType = request.getContentType ();
                    if (contentType.contains ("json")) {
                        temp = new String (IOUtil.read (request.getInputStream ()));
                    } else {
                        temp = request.getParameter (wp.name);
                    }
                    break;
                case Path:
                    temp = values.get (wp.name);
                    break;
                default:
                    throw new IllegalArgumentException ("unknown location: " + wp.location);
            }

            switch (wp.type) {
                case string:
                    args[i] = temp;
                    break;
                case integer:
                    args[i] = Integer.parseInt (temp);
                    break;
                case long_integer:
                    args[i] = Long.parseLong (temp);
                    break;
                case bool:
                    args[i] = Boolean.parseBoolean (temp);
                    break;
                case datetime:
                    try {
                        args[i] = new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss").parse (temp);
                    } catch (ParseException pe) {
                        try {
                            args[i] = new SimpleDateFormat ("yyyy-MM-dd").parse (temp);
                        } catch (ParseException ex) {
                            throw new RuntimeException (ex);
                        }
                    }
                    break;
                case raw:
                    Class<?> parameterType = handler.method.method.getParameterTypes ()[i];
                    args[i] = translate (temp, parameterType);
                    break;
            }
        }
        return args;
    }

    private Object translate (String expression, Class<?> type) {
        if (type == int.class || type == Integer.class) {
            return Integer.parseInt (expression);
        }
        if (type == byte.class || type == Byte.class) {
            return Byte.parseByte (expression);
        }
        if (type == char.class || type == Character.class) {
            return expression.length () == 0 ? '\u0000' : expression.charAt (0);
        }
        if (type == short.class || type == Short.class) {
            return Short.parseShort (expression);
        }
        if (type == long.class || type == Long.class) {
            return Long.parseLong (expression);
        }
        if (type == boolean.class || type == Boolean.class) {
            return Boolean.parseBoolean (expression);
        }
        if (type == float.class || type == Float.class) {
            return Float.parseFloat (expression);
        }
        if (type == double.class || type == Double.class) {
            return Double.parseDouble (expression);
        }
        if (type.isAssignableFrom (String.class)) {
            return expression;
        }
        if (type == BigDecimal.class) {
            return new BigDecimal (expression);
        }
        if (type == BigInteger.class) {
            return new BigInteger (expression);
        }
        if (type == Date.class) {
            return toDate (expression);
        }
        if (type == java.sql.Date.class) {
            return new java.sql.Date (toDate (expression).getTime ());
        }
        if (type == java.sql.Timestamp.class) {
            return new java.sql.Timestamp (toDate (expression).getTime ());
        }
        return GsonHelper.getGson ().fromJson (expression, type);
    }

    private Date toDate (String expression) {
        try {
            return new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss").parse (expression);
        } catch (ParseException ex) {
            try {
                return new SimpleDateFormat ("yyyy-MM-dd").parse (expression);
            } catch (ParseException e) {
                throw new RuntimeException (e);
            }
        }
    }
}
